package org.xxpay.pay.ctrl.payment;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.xxpay.core.common.constant.MchConstant;
import org.xxpay.core.common.constant.PayConstant;
import org.xxpay.core.common.constant.PayEnum;
import org.xxpay.core.common.domain.api.AbstractRes;
import org.xxpay.core.common.domain.api.ApiBuilder;
import org.xxpay.core.common.exception.PayException;
import org.xxpay.core.common.util.*;
import org.xxpay.core.common.vo.OrderCostFeeVO;
import org.xxpay.core.entity.*;
import org.xxpay.pay.channel.saaspay.SaaspayConfig;
import org.xxpay.pay.ctrl.common.BaseController;
import org.xxpay.pay.mq.Mq4PayOrderLog;
import org.xxpay.pay.mq.queue.MqQueue4Statistics;
import org.xxpay.pay.service.LocalDynamicChannelService;
import org.xxpay.pay.service.PayOrderService;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;

/**
 * @Description: 支付订单,包括:统一下单,订单查询,补单等接口
 * @author dingzhiwei jmdhappy@126.com
 * @date 2017-07-05
 * @version V1.0
 * @Copyright: www.xxpay.org
 */
@Controller
public class PayOrderController extends BaseController {

    private final MyLog _log = MyLog.getLog(PayOrderController.class);

    @Autowired
    private Mq4PayOrderLog mqPayOrderLog;

    @Autowired
    private PayOrderService payOrderService;

    @Autowired private LocalDynamicChannelService localDynamicChannelService;

    @Autowired
    private MqQueue4Statistics mqQueue4Statistics;


    /**
     * 统一下单接口:
     * 1)先验证接口参数以及签名信息
     * 2)验证通过创建支付订单
     * 3)根据商户选择渠道,调用支付服务进行下单
     * 4)返回下单数据
     * @param request
     * @return
     */
    @RequestMapping(value = "/api/pay/create_order")
    @ResponseBody
    public AbstractRes payOrder(HttpServletRequest request) {
        _log.info("###### 开始接收商户统一下单请求 ######");
        String logPrefix = "【商户统一下单】";
        String mchOrderNo = "";
        String payOrderId = "";
        try {
            JSONObject po = getJsonParam4Xss(request);
            // 此处临时取商户订单号,为了记录记录订单日志使用
            mchOrderNo = po.getString("mchOrderNo"); //
            _log.info("{}请求参数:{}", logPrefix, po);
            JSONObject payContext = new JSONObject();
            PayOrder payOrder = null;
            // 验证参数有效性
            Object object = validateParams(po, payContext, request);
            if (object instanceof String) {
                _log.info("{}参数校验不通过:{}", logPrefix, object);
                return buildAbstractRes(ApiBuilder.bizError(object.toString()), mchOrderNo, null);
            }
            if (object instanceof PayOrder) payOrder = (PayOrder) object;
            if(payOrder == null) {
                return buildAbstractRes(ApiBuilder.bizError("支付中心下单失败"), mchOrderNo, null);
            }
            payOrderId = payOrder.getPayOrderId();
            String channelId = payOrder.getChannelId();
            String channelName = channelId.substring(0, channelId.indexOf("_"));

            AbstractRes res = localDynamicChannelService.callPaymentMethod(channelName, payOrder, true);
            payOrderId = payOrder.getPayOrderId(); //重新获取payOrderId参数

            //处理签名
            res.autoGenSign(payContext.getString("key"));

            //发送下单统计通知
            mqQueue4Statistics.sendPayStatistics(payOrder, MchConstant.ORDER_STATUS_INIT);
            return buildAbstractRes(res, mchOrderNo, payOrderId);

        }catch (PayException e) {
            _log.error(e, e.getMessage());
            return buildAbstractRes(ApiBuilder.bizError(e.getMessage()), mchOrderNo, payOrderId);
        }catch (Exception e) {
            _log.error(e, "");
            return buildAbstractRes(ApiBuilder.bizError(PayEnum.ERR_0010), mchOrderNo, payOrderId);
        }
    }

    /**
     * 构建响应结果
     * @param res
     * @return
     */
    AbstractRes buildAbstractRes(AbstractRes res, String mchOrderNo, String payOrderId) {
        _log.info(">>>>>> PAY开始处理支付订单下单响应日志通知 <<<<<<");
        // 处理参数
        JSONObject json = new JSONObject();
        json.put("params", res.toJSONString());
        json.put("type", PayConstant.PAY_ORDER_LOG_TYPE_ORDER_RESPONSE);
        json.put("payOrderId", payOrderId);
        json.put("mchOrderNo", mchOrderNo);
        try {
            // 发送下单日志通知
            mqPayOrderLog.send(json.toJSONString());
        } catch (Exception e) {
            _log.error(e, "发送下单响应日志失败，params={}, payOrderId={}, mchOrderNo={}", res, payOrderId, mchOrderNo);
        }
        _log.info(">>>>>> PAY处理支付订单下单响应日志通知完成 <<<<<<");
        // 直接返回
        return res;
    }

    /**
     * 服务端跳转
     * @param request
     * @return
     * @throws ServletException
     * @throws IOException
     */
    @RequestMapping("/api/jump.htm")
    public String toPay(HttpServletRequest request, ModelMap model) throws ServletException, IOException {
        String params = request.getParameter("params");
        if(StringUtils.isNotBlank(params)) {
            String jumpForm = new String(MyBase64.decode(params));
            model.put("jumpForm", jumpForm);
        }else {
            model.put("jumpForm", "跳转出现异常,请联系管理员.");
        }
        return "payment/jump";
    }

    /**
     * 验证创建订单请求参数,参数通过返回JSONObject对象,否则返回错误文本信息
     * @param params
     * @return
     */
    private Object validateParams(JSONObject params, JSONObject payContext, HttpServletRequest request) {

        //验证公共请求参数
        String commonErrorMsg = super.validateCommonParams(params);
        if(commonErrorMsg != null) return commonErrorMsg;

        String riskLog = "[支付风控]";
        // 验证请求参数,参数有问题返回错误提示
        String errorMessage;
        // 支付参数
        String mchId = params.getString("mchId"); 			    // 商户ID
        String appId = params.getString("appId");              // 应用ID
        String productId = params.getString("productId");      // 支付产品ID
        String mchOrderNo = params.getString("mchOrderNo"); 	// 商户订单号
        String amount = params.getString("amount"); 		    // 支付金额（单位分）
        String currency = params.getString("currency");        // 币种
        String clientIp = params.getString("clientIp");	    // 客户端IP
        String device = params.getString("device"); 	        // 设备
        String extra = params.getString("extra");		        // 特定渠道发起时额外参数
        String param1 = params.getString("param1"); 		    // 扩展参数1
        String param2 = params.getString("param2"); 		    // 扩展参数2
        String returnUrl = params.getString("returnUrl"); 		// 支付结果同步请求url
        String notifyUrl = params.getString("notifyUrl"); 		// 支付结果回调URL
        String sign = params.getString("sign"); 				// 签名
        String subject = params.getString("subject");	        // 商品主题
        String body = params.getString("body");	                // 商品描述信息
        String payPassAccountId = params.getString("payPassAccountId"); // 支付通道子账户ID,非必填

        // 验证请求参数有效性（必选项）
        Long mchIdL;
        if(StringUtils.isBlank(mchId) || !NumberUtils.isDigits(mchId)) {
            errorMessage = "请求参数[mchId]不能为空且为数值类型.";
            return errorMessage;
        }
        mchIdL = Long.parseLong(mchId);
        // 查询商户信息
        MchInfo mchInfo = commonService.mchInfoService.findByMchId(mchIdL);
        if(mchInfo == null) {
            errorMessage = "商户不存在[mchId="+mchId+"].";
            return errorMessage;
        }
        if(mchInfo.getStatus() != MchConstant.PUB_YES) {
            errorMessage = "商户状态不可用[mchId="+mchId+"].";
            return errorMessage;
        }
        // 判断请求IP是否允许
        String clintIp = IPUtility.getClientIp(request);
        boolean isAllow = XXPayUtil.ipAllow(clintIp, mchInfo.getPayWhiteIp(), mchInfo.getPayBlackIp());
        if(!isAllow) {
            errorMessage = "IP["+clintIp+"]不允许访问";
            return errorMessage;
        }

        Integer productIdI = null;
        if(StringUtils.isBlank(productId) || !NumberUtils.isDigits(productId)) {
            errorMessage = "请求参数[productId]不能为空且为数值类型.";
            return errorMessage;
        }
        productIdI = Integer.parseInt(productId);

        if(StringUtils.isBlank(mchOrderNo)) {
            errorMessage = "请求参数[mchOrderNo]不能为空.";
            return errorMessage;
        }
        if(mchOrderNo.length() > 30) {
            return "请求参数[mchOrderNo]长度不得大于30位.";
        }

        if(!NumberUtils.isDigits(amount)) {
            errorMessage = "请求参数[amount]应为数值类型.";
            return errorMessage;
        }
        Long amountL = Long.parseLong(amount);
        if(amountL <= 0) {
            errorMessage = "请求参数[amount]必须大于0.";
            return errorMessage;
        }
        if(amountL > Long.MAX_VALUE) {
            return "请求参数[amount]金额过大.";
        }

        if(StringUtils.isBlank(currency)) {
            errorMessage = "请求参数[currency]不能为空.";
            return errorMessage;
        }
        if(currency.length() > 3) {
            return "请求参数[currency]长度不得大于3位.";
        }
        if(StringUtils.isBlank(notifyUrl)) {
            errorMessage = "请求参数[notifyUrl]不能为空.";
            return errorMessage;
        }
        if(notifyUrl.length() > 128) {
            return "请求参数[notifyUrl]长度不得大于128位.";
        }
        if(StringUtils.isBlank(subject)) {
            errorMessage = "请求参数[subject]不能为空.";
            return errorMessage;
        }
        if(subject.length() > 64) {
            return "请求参数[subject]长度不得大于64位.";
        }
        if(StringUtils.isBlank(body)) {
            errorMessage = "请求参数[body]不能为空.";
            return errorMessage;
        }
        if(body.length() > 256) {
            return "请求参数[body]长度不得大于256位.";
        }
        if(StringUtils.isBlank(clientIp)) {
            clientIp = IPUtility.getClientIp(request);
        }

        if(clientIp != null && clientIp.length() > 128){
            return "请求参数[clientIp]长度不得大于128位.";
        }
        if(device != null && device.length() > 64){
            return "请求参数[device]长度不得大于64位.";
        }
        if(extra != null && extra.length() > 512){
            return "请求参数[extra]长度不得大于512位.";
        }
        if(param1 != null && param1.length() > 64){
            return "请求参数[param1]长度不得大于64位.";
        }
        if(param2 != null && param2.length() > 64){
            return "请求参数[param2]长度不得大于64位.";
        }
        if(returnUrl != null && returnUrl.length() > 128){
            return "请求参数[returnUrl]长度不得大于128位.";
        }

        String channelUser = "";

        // 签名信息
        if (StringUtils.isEmpty(sign)) {
            errorMessage = "请求参数[sign]不能为空.";
            return errorMessage;
        }

        // 查询应用信息
        if(StringUtils.isNotBlank(appId)) {
            MchApp mchApp = commonService.mchAppService.findByMchIdAndAppId(mchIdL, appId);
            if(mchApp == null) {
                errorMessage = "应用不存在[appId=" + appId + "]";
                return errorMessage;
            }
            if(mchApp.getStatus() != MchConstant.PUB_YES) {
                errorMessage = "应用状态不可用[appId=" + appId + "]";
                return errorMessage;
            }
        }

        if(commonService.payOrderService.hasOrderByMchOrderNo(mchIdL, mchOrderNo)){
            return "该商户订单已存在[mchOrderNo=" + mchOrderNo + "]";
        }

        String key = mchInfo.getPrivateKey();
        if (StringUtils.isBlank(key)) {
            errorMessage = "商户私钥为空,请配置商户私钥[mchId="+mchId+"].";
            return errorMessage;
        }
        payContext.put("key", key);

        // 暂时移除sign
        params.remove("sign");	// 不参与签名
        // 获取签名串
        String signStr = PayDigestUtil.getSignStr(params, key);
        // 签名结果
        String signValue = PayDigestUtil.getSign(signStr);

        // 放回sign
        params.put("sign", sign);
        // 验证签名数据
        if (!sign.equalsIgnoreCase(signValue)) {
            // 发送订单日志通知 -- 下单失败
            JSONObject tempParam = (JSONObject)params.clone();
            tempParam.remove("sign");
            doPayOrderLog(PayConstant.PAY_ORDER_LOG_TYPE_MCH_PAYORDER, signStr, signValue, params.toJSONString(), null);
            errorMessage = "验证签名不通过.正确签名结果:"+signValue+",签名串参考:" + PayDigestUtil.getSignStr(tempParam, "替换商户私钥");
            return errorMessage;
        }

        // 查询商户对应的支付渠道
        String channelMchId;
        String channelType;
        String channelId;
        BigDecimal mchRate;
        BigDecimal parentAgentRate = null;
        BigDecimal agentRate = null;
        Integer passageAccountId;

        MchPayPassage mchPayPassage = commonService.mchPayPassageService.findByMchIdAndProductId(mchIdL, productIdI);

        if(mchPayPassage == null) {
            errorMessage = "商户没有该产品的支付通道[productId="+productId+",mchId="+mchId+"]";
            return errorMessage;
        }
        if(mchPayPassage.getStatus() != MchConstant.PUB_YES) {
            errorMessage = "商户该产品的支付通道[productId="+productId+",mchId="+mchId+"]已关闭";
            return errorMessage;
        }

        //私有商户 & 运营平台未配置该支付产品
        if(mchInfo.getType() == MchConstant.MCH_TYPE_PRIVATE && !mchInfo.getSupportPayProductList().contains(productIdI)){
            return "商户没有该产品的支付通道[productId="+productId+",mchId="+mchId+"]";
        }

        // 支付通道ID
        Integer payPassageId = null;
        PayPassageAccount payPassageAccount = null;
        if(StringUtils.isNotBlank(payPassAccountId) && NumberUtils.isDigits(payPassAccountId)) {
            payPassageAccount = commonService.payPassageAccountService.findById(Integer.parseInt(payPassAccountId));
            if(payPassageAccount == null || payPassageAccount.getStatus() != MchConstant.PUB_YES) {
                errorMessage = "传入子账户不可用[payPassAccountId="+payPassAccountId+",mchId="+mchId+"]";
                return errorMessage;
            }
            payPassageId = payPassageAccount.getPayPassageId();
        }else {
            // 获取通道子账户
            Object obj = payOrderService.getPayPassageAccount(mchPayPassage, riskLog, amountL);
            if(obj instanceof String) {
                return obj;
            }
            if(obj instanceof PayPassageAccount) {
                payPassageAccount = (PayPassageAccount) obj;
                payPassageId = payPassageAccount.getPayPassageId();
            }
        }

        // 判断支付通道
        if(payPassageId == null) {
            errorMessage = "无法取得可用的支付通道[productId="+productId+",mchId="+mchId+"]";
            return errorMessage;
        }

        PayPassage payPassage = commonService.payPassageService.findById(payPassageId);
        if(payPassage == null){
            return "无法取得可用的支付通道[payPassageId="+payPassageId+"]";
        }

        // 判断子账户
        if(payPassageAccount == null) {
            errorMessage = "该支付通道没有可用子账户[payPassageId="+payPassageId+"]";
            return errorMessage;
        }

        BigDecimal payPassageRate = payPassage.getPassageRate(); //通道费率【中间值】

        passageAccountId = payPassageAccount.getId();
        channelMchId = payPassageAccount.getPassageMchId();
        channelType = payPassageAccount.getIfTypeCode();
        channelId = payPassageAccount.getIfCode();
        BigDecimal channelOrSupplierSetRate = payPassageAccount.getPassageRate();  //平台子账号 or MS的配置费率值
        mchRate = mchPayPassage.getMchRate();

        // 处理二级代理商
        Long agentId = mchInfo.getAgentId();
        Long parentAgentId = mchInfo.getParentAgentId();
        if(agentId != null) {
            AgentPassage agentPassage = commonService.agentPassageService.findByAgentIdAndProductId(agentId, productIdI);
            if(agentPassage != null && agentPassage.getStatus() == MchConstant.PUB_YES) {
                agentRate = agentPassage.getAgentRate();
            }
            if(agentRate == null) {
                errorMessage = "请设置二级代理商费率";
                return errorMessage;
            }
            //处理一级代理商
            if(parentAgentId != null && parentAgentId != 0) {
                AgentPassage agentPassage2 = commonService.agentPassageService.findByAgentIdAndProductId(parentAgentId, productIdI);
                if(agentPassage2 != null && agentPassage2.getStatus() == MchConstant.PUB_YES) {
                    parentAgentRate = agentPassage2.getAgentRate();
                }
                if(parentAgentRate == null) {
                    errorMessage = "请设置一级代理商费率";
                    return errorMessage;
                }
            }
        }

        if(channelType == null || channelId == null) {
            errorMessage = "商户没有该产品的支付通道[productId="+productId+",mchId="+mchId+",channelType="+channelType+",channelId="+channelId+"]";
            return errorMessage;
        }

        // 根据不同渠道,判断extra参数
        if(PayConstant.PAY_CHANNEL_WX_JSAPI.equalsIgnoreCase(channelId)) {
            if(StringUtils.isEmpty(extra)) {
                errorMessage = "request params[extra] error.";
                return errorMessage;
            }
            JSONObject extraObject = JSON.parseObject(extra);
            String openId = extraObject.getString("openId");
            if(StringUtils.isBlank(openId)) {
                errorMessage = "request params[extra.openId] error.";
                return errorMessage;
            }
            channelUser = openId;
        }else if(PayConstant.PAY_CHANNEL_WX_MWEB.equalsIgnoreCase(channelId)) {
            if(StringUtils.isEmpty(extra)) {
                errorMessage = "请求参数[extra]不能为空.";
                return errorMessage;
            }
            JSONObject extraObject = JSON.parseObject(extra);
            String sceneInfo = extraObject.getString("sceneInfo");
            if(StringUtils.isBlank(sceneInfo)) {
                errorMessage = "请求参数[extra.sceneInfo]不能为空.";
                return errorMessage;
            }
            if(StringUtils.isBlank(clientIp)) {
                errorMessage = "请求参数[clientIp]不能为空.";
                return errorMessage;
            }
        }else if(PayConstant.PAY_CHANNEL_ACCOUNTPAY_BALANCE.equalsIgnoreCase(channelId)) {
            if(StringUtils.isEmpty(extra)) {
                errorMessage = "请求参数[extra]不能为空.";
                return errorMessage;
            }
            JSONObject extraObject = JSON.parseObject(extra);
            String userId = extraObject.getString("userId");
            if(StringUtils.isBlank(userId)) {
                errorMessage = "请求参数[extra.userId]不能为空.";
                return errorMessage;
            }
            channelUser = userId;
        }else if(PayConstant.PAY_CHANNEL_ALIPAY_BAR.equalsIgnoreCase(channelId) ||PayConstant.PAY_CHANNEL_WX_BAR.equalsIgnoreCase(channelId) ) {
            if(StringUtils.isEmpty(extra)) return "请求参数[extra]不能为空.";
        }



        // 验证参数通过,返回JSONObject对象
        PayOrder payOrder = new PayOrder();
        if("saaspay".equals(channelType)){
            payOrder.setPayOrderId(SaaspayConfig.SYSTEM_ORDER_CODE+MySeq.getPay().substring(1,MySeq.getPay().length()));
        }else{
            payOrder.setPayOrderId(MySeq.getPay());
        }
        payOrder.setMchId(mchIdL);
        payOrder.setMchType(mchInfo.getType());
        payOrder.setAppId(appId);
        payOrder.setMchOrderNo(mchOrderNo);
        payOrder.setAgentId(agentId);
        payOrder.setParentAgentId(parentAgentId);
        payOrder.setProductId(productIdI);                          // 支付产品ID
        payOrder.setProductType(mchPayPassage.getProductType());    // 产品类型
        payOrder.setPassageId(payPassageId);                        // 支付通道ID
        payOrder.setPassageAccountId(passageAccountId);             // 支付通道账户ID
        payOrder.setChannelType(channelType);
        payOrder.setChannelId(channelId);
        payOrder.setAmount(amountL);
        payOrder.setOrderAmount(amountL);
        payOrder.setPaymentAmount(amountL);
        payOrder.setCurrency(currency);
        payOrder.setClientIp(clientIp);
        payOrder.setDevice(device);
        payOrder.setSubject(subject);
        payOrder.setBody(body);
        payOrder.setExtra(extra);
        payOrder.setChannelMchId(channelMchId);
        payOrder.setChannelUser(channelUser);

        payOrder.setAgentRate(agentRate);
        payOrder.setParentAgentRate(parentAgentRate);
        payOrder.setMchRate(mchRate);

        payOrder.setChannelRate(channelOrSupplierSetRate); //渠道费率（MS配置的费率/平台子账号费率）
        payOrder.setPayPassageRate(payPassageRate); //通道费率（中间费率值）

        //smid: 支付宝直付通接口
        String smid = payPassageAccount.getPsVal("availableSMID", String.class);

        if(StringUtils.isNotEmpty(smid)){

            payOrder.setChannelSMId(smid);

            PayAlizftMchInfo updateRecrod = new PayAlizftMchInfo();
            updateRecrod.setLastInTime(new Date());

            PayAlizftMchInfo condition = new PayAlizftMchInfo();
            condition.setPassageTemplateId(payPassageAccount.getTemplateId());
            condition.setSmid(smid);
            commonService.payAlizftMchInfoService.updateByCondition(updateRecrod, condition, null);
        }

        // 重新计算订单:渠道成本费用,代理商费用,商户入账,平台利润
        OrderCostFeeVO orderCostFeeVO = XXPayUtil.calOrderCostFeeAndIncome(amountL, payOrder.getChannelRate(), payOrder.getAgentRate(), payOrder.getParentAgentRate(), payOrder.getMchRate(),
                payOrder.getPayPassageRate());

        //重新计算订单 设置订单分润数据
        XXPayUtil.commonSetPayOrderProfit(payOrder, orderCostFeeVO);

        payOrder.setParam1(param1);
        payOrder.setParam2(param2);
        payOrder.setNotifyUrl(notifyUrl);
        payOrder.setReturnUrl(returnUrl);
        payOrder.setIsDivision(payPassage.getIsDivision());  //是否分账

        //使用程序时间，保证创建时间与 过期时间的时间差计算结果一致
        Date nowTime = new Date();
        payOrder.setCreateTime(nowTime);
        PayInterfaceType payInterfaceType = commonService.payInterfaceTypeService.findByCode(payPassage.getIfTypeCode());
        if(payInterfaceType != null && payInterfaceType.getOrderExpire() != null) { // 如果支付接口类型配置了超时时间
            payOrder.setExpireTime(org.apache.commons.lang3.time.DateUtils.addMinutes(nowTime, payInterfaceType.getOrderExpire()));
        }else {
            payOrder.setExpireTime(org.apache.commons.lang3.time.DateUtils.addMinutes(nowTime, PayConstant.PAY_EXPIRE_TIME));
        }

        // 发送订单日志通知 -- 下单成功
        doPayOrderLog(PayConstant.PAY_ORDER_LOG_TYPE_MCH_PAYORDER, signStr, signValue, params.toJSONString(), payOrder.getPayOrderId());

        return payOrder;
    }

    /**
     * 处理支付订单日志通知
     */
    public void doPayOrderLog(Byte type, String signStr, String signValue, String params, String payOrderId) {
        _log.info(">>>>>> PAY开始处理支付订单日志通知 <<<<<<");
        // 处理参数
        JSONObject json = new JSONObject();
        json.put("signStr", signStr);
        json.put("signValue", signValue);
        json.put("params", params);
        json.put("type", type);
        json.put("payOrderId", payOrderId);
        try {
            // 发送下单日志通知
            mqPayOrderLog.send(json.toJSONString());
        } catch (Exception e) {
            _log.error(e, "发送订单日志失败，params={}, type={}", params, type);
        }
        _log.info(">>>>>> PAY处理支付订单日志通知完成 <<<<<<");
    }
}
